package com.zx.crowdfunding.mvc.config;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

import com.google.gson.Gson;
import com.zx.crowdfunding.constant.CrowdFundingConstant;
import com.zx.crowdfunding.exception.LoginAcctAlreadyInUseException;
import com.zx.crowdfunding.exception.LoginFailedException;
import com.zx.crowdfunding.util.CrowdFundingUtil;
import com.zx.crowdfunding.util.ResultEntity;

/**
 * 基于注解的异常处理器类
 * @author 郑雪
 * @date 2021-12-07
 */
@ControllerAdvice
public class CrowdFundingExceptionResolver {
	
	/**
	 * SpringSecurity访问被拒绝的异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = AccessDeniedException.class)
	public ModelAndView resolveAccessDeniedException(AccessDeniedException exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 返回的视图名称
		String viewName = "system-error";
		
		// 返回ModelAndView对象
		return commonResolve(new AccessDeniedException(CrowdFundingConstant.MESSAGE_ACCESS_DENIED), viewName, request, response);
	}
	
	/**
	 * 全局异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = Exception.class)
	public ModelAndView resolveException(Exception exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		exception.printStackTrace();
		// 返回的视图名称
		String viewName = "system-error";
		
		// 返回ModelAndView对象
		return commonResolve(exception, viewName, request, response);
	}
	
	/**
	 * 自定义的账号已存在异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = LoginAcctAlreadyInUseException.class)
	public ModelAndView resolveLoginAcctAlreadyInUseException(LoginAcctAlreadyInUseException exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 用户自定义的要返回的视图名称
		String viewName = exception.getViewName();
		if(viewName == null || "".equals(viewName)){
			viewName = "system-error";
		}
		
		// 返回ModelAndView对象
		return commonResolve(exception, viewName, request, response);
	}
	
	/**
	 * 自定义的登录失败异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = LoginFailedException.class)
	public ModelAndView resolveLoginFailedException(LoginFailedException exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 返回的视图名称
		String viewName = "admin-login";
		
		// 返回ModelAndView对象
		return commonResolve(exception, viewName, request, response);
	}

	/**
	 * 算术运算异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = ArithmeticException.class)
	public ModelAndView resolveMathException(ArithmeticException exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 返回的视图名称
		String viewName = "system-error";
		
		// 返回ModelAndView对象
		return commonResolve(exception, viewName, request, response);
	}

	/**
	 * 空指针异常处理
	 * @param exception 实际捕获到的异常类型
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	@ExceptionHandler(value = NullPointerException.class)
	public ModelAndView resolveNullPointerException(NullPointerException exception, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 返回的视图名称
		String viewName = "system-error";
		
		// 返回ModelAndView对象
		return commonResolve(exception, viewName, request, response);
	}
	
	/**
	 * 通用的异常处理器
	 * 将一个具体的异常类型和一个方法关联起来
	 * @param exception 实际捕获到的异常类型
	 * @param viewName 返回的视图名称
	 * @param request 当前请求对象
	 * @param response 当前相应对象
	 * @return
	 * 		ModelAndView：如果当前请求不是Ajax请求，会返回一个视图<br>
	 * 		ResultEntity：如果当前请求是Ajax请求，会通过原生的response对象返回JSON字符串
	 * @throws IOException 
	 */
	private ModelAndView commonResolve(Exception exception, String viewName, 
			HttpServletRequest request, HttpServletResponse response) throws IOException{
		
		// 1.判断当前请求类型
		boolean judgeRequest = CrowdFundingUtil.judgeRequestType(request);
		
		// 2.如果是Ajax请求
		if(judgeRequest){
			
			// 3.创建ResultEntity对象
			ResultEntity<Object> resultEntity = ResultEntity.failed(exception.getMessage());
			
			// 4.创建Gson对象
			Gson gson = new Gson();
			
			// 5.将ResultEntity对象转换为JSON字符串
			String json = gson.toJson(resultEntity);
			
			// 6.将JSON字符串作为响应体返回给浏览器
			response.getWriter().write(json);
			
			// 7.由于上面已经通过原生的response对象返回了响应，所以不提供ModelAndView对象
			return null;
		}
		
		// 8.如果不是Ajax请求，则创建ModelAndView对象
		ModelAndView modelAndView = new ModelAndView();
		
		// 9.将Exception对象存入模型
		modelAndView.addObject(CrowdFundingConstant.ATTR_NAME_EXCEPTION, exception);
		
		// 10.设置对应的视图名称
		modelAndView.setViewName(viewName);
		
		// 11.返回ModelAndView对象
		return modelAndView;
	}
}
